/***************************************************************************
 *   oboe.core.RenderSettings                                              *
 *   Copyright (C) 2011 by Felipe Manga                                    *
 *   lufeboma@gmail.com                                                    *
 *                                                                         *
 *   Permission is hereby granted, free of charge, to any person obtaining *
 *   a copy of this software and associated documentation files (the       *
 *   "Software"), to deal in the Software without restriction, including   *
 *   without limitation the rights to use, copy, modify, merge, publish,   *
 *   distribute, sublicense, and/or sell copies of the Software, and to    *
 *   permit persons to whom the Software is furnished to do so, subject to *
 *   the following conditions:                                             *
 *                                                                         *
 *   The above copyright notice and this permission notice shall be        *
 *   included in all copies or substantial portions of the Software.       *
 *                                                                         *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
 ***************************************************************************/

package oboe.core
{
	import flash.display.BitmapData;
	import flash.display.Graphics;
	import flash.display.TriangleCulling;
	
	import oboe.controllers.SceneController;
	import oboe.math.Frame;
	import oboe.math.Number2D;
	import oboe.math.Number3D;
	import oboe.math.Rotator;

	public class RenderSettings
	{
		private static const tmp:Number3D=new Number3D(0,0,0);
		
		public static const finalPosition:Number3D = new Number3D(0,0,0);
		public var rotator:Rotator;
		public var position:Number3D;
		public var rotation:Number3D;
		public var scale:Number3D;
		public var forceZSort:Number;
		
		public var containerShiftOut:Number2D;
		public var containerShiftIn:Number2D;
		
		public var frame:Number;
		public var frameLoopStart:int;
		public var frameLoopEnd:int;
		public function setAnimation( frame:Number, start:int, end:int ):void
		{
			this.frame = frame;
			this.frameLoopStart = start;
			this.frameLoopEnd = end;
		}
		
		public var texture:BitmapData;
		public var color:uint;
		
		public static const MODE_CACHE_ONLY:int = 1;
		public static const MODE_DRAW_ONLY:int = 2;
		public static const MODE_CACHE_DRAW:int = 3;
		public var mode:int;
		public var cacheCamera:Boolean = false;
		
		public var target:Graphics;
		
		public static const CULL_BACK:String = TriangleCulling.POSITIVE;
		public static const CULL_FRONT:String = TriangleCulling.NEGATIVE;
		public static const CULL_NONE:String = TriangleCulling.NONE;
		public var culling:String;
		
		public static const PROJECT_PARALLEL:int = 1;
		public static const PROJECT_PERSPECTIVE:int = 2;
		public static const PROJECT_ISOMETRIC:int = 3;
		public var projection:int;
		
		public var focalLength:Number;
		
		private var invPrecision:Number;
		public var rotationGranularity:Number;
		public var positionGranularity:Number;
		
		public var debugCache:Boolean;
		
		
		public const SORT_FORCE:Function = function():Number{ return forceZSort; };
		public const SORT_PARALLEL:Function = SORT_PERSPECTIVE;
		public const SORT_ISOMETRIC:Function = function():Number{ return -(position.x+position.z); };
		public const SORT_PERSPECTIVE:Function = function():Number
		{
			if( SceneController._camera_position )
			{
				tmp.copy3( position ).sub3( SceneController._camera_position );
				if( SceneController._camera_rotator ) SceneController._camera_rotator.apply( tmp );
				return tmp.z;
			}
			return position.z; 
		};
		
		
		public function RenderSettings()
		{
			this.position = new Number3D(0,0,0);
			this.rotation = new Number3D(0,0,0);
			this.scale = new Number3D(1,1,1);
			this.rotator = new Rotator(0,0,0);
			
			this.forceZSort = NaN;
			
			this.containerShiftOut = new Number2D();
			this.containerShiftIn = new Number2D( Oboe.flags["stageWidth"]/2, Oboe.flags["stageHeight"]/2 );
			
			this.frame = this.frameLoopStart = this.frameLoopEnd = 0;
			this.projection = Oboe.flags["PROJECTION"];
			this.color = 0xFF5588AA;
			this.mode = MODE_CACHE_DRAW;
			this.culling = Oboe.flags["CULLING"];
			
			this.precision = Oboe.flags["PRECISION"];
			this.positionGranularity = 0.01;
			this.rotationGranularity = 1;
			this.setFOV(Oboe.flags["stageWidth"], Oboe.flags["FOV"] );
			this.debugCache = Oboe.flags["DEBUG_3D_CACHE"];
		}
		
		public function setFOV( containerWidth:Number, angle:Number ):void
		{
			this.focalLength = (containerWidth/2) / Math.tan((Math.PI/180)*(angle/2));
		}
		
		public function set precision( p:Number ):void
		{
			this.invPrecision = 1/p;
		}
		
		public function get precision():Number
		{
			return 1/this.invPrecision;
		}
		
		public function getScreenCoordinates( out:Number2D ):void
		{
			switch( this.projection )
			{
				case RenderSettings.PROJECT_PARALLEL:
					out.set( 
						this.position.x-SceneController._camera_position.x, 
						-this.position.y+SceneController._camera_position.y 
					);
					break;
				case RenderSettings.PROJECT_ISOMETRIC:
					tmp.copy3( this.position ).sub3( SceneController._camera_position );
					out.set( -tmp.x+tmp.z, -tmp.y + (tmp.x+tmp.z)*0.5 );
					break;
				case RenderSettings.PROJECT_PERSPECTIVE:
					if( SceneController._camera_position )
					{
						tmp.copy3( this.position ).sub3( SceneController._camera_position );
					}
					else
					{
						tmp.copy3( this.position );
					}
					if( SceneController._camera_rotator )
					{
						SceneController._camera_rotator.apply( tmp );
					}
					var z:Number = this.focalLength / (this.focalLength + tmp.z);
					out.x = this.containerShiftOut.x + tmp.x * z;
					out.y = this.containerShiftOut.y - tmp.y * z;
					break;
			}
		}
		
		public function getCacheKey():String
		{
			var acc:String;
			tmp.copy3( this.rotation );
			if( this.cacheCamera && this.projection == PROJECT_PERSPECTIVE && SceneController._camera_rotator )
			{
				tmp.sub3( SceneController._camera_rotator );
			}
			var pi2:Number=Math.PI*2;
			tmp.x %= pi2;
			tmp.y %= pi2;
			tmp.z %= pi2;
			tmp.mul( this.invPrecision*this.rotationGranularity ).floor();
			acc = tmp.x + "|" + tmp.y + "|" + tmp.z + "|";
			if( this.projection == PROJECT_PERSPECTIVE )
			{
				tmp.copy3( this.position );
				if( this.cacheCamera ) tmp.sub3( SceneController._camera_position );
				tmp.mul( this.invPrecision * this.positionGranularity ).floor();
				acc += tmp.x + "|" + tmp.y + "|" + tmp.z;
			}
			return acc;
		}
	}
}